#include <QDebug>
#include "layoutmanager.h"
#include "indicators.h"
#include "../defaultwindowfinder.h"
#include "imitatorwidget.h"
#include "basedock.h"
#include "splitter.h"
#include "containers.h"
#include "tabbedcontainer.h"


const QPoint nullpoint(0,0);

QWidget* pierce(QWidget* widget, QPoint point, bool chase_indicators){
QWidget* child;
//    QPoint realpoint = widget->mapFromGlobal(point);
//    QWidget* child = widget->childAt(realpoint);
//    if (child == 0)
//        return widget;
//    if (!qobject_cast<QPreviewWidget*>(child)){
//        return pierce(child,point);
//    }

    QObjectList children = widget->children();
    for (int i = children.count()-1; i>=0;i--){
        child = qobject_cast<QWidget*>(children.at(i));
        if (!child)
            continue;
        if (!child->isVisible())
            continue;
        QPreviewWidget* preview = qobject_cast<QPreviewWidget*>(child);
        if (preview)
            continue;



        QRect rect = child->rect();
        rect.moveTopLeft(child->mapToGlobal(nullpoint));
        if (rect.contains(point)){
            QIndicator* indicator = qobject_cast<QIndicator*>(child);
            if (indicator){
                if (!chase_indicators)
                    continue;
                QWidget* target = indicator->target();
                if (target == widget)
                    return target;
                return pierce(target, point, chase_indicators);
            }
            return pierce(child,point, chase_indicators);

        }
    }
    //if we have not returned till now, there is nowhere to go
    return widget;
}

QDockItem * QLayoutManager::itemFromPoint(QDockItem* parent, const QPoint &point, bool chase_indicators)
{
    if (parent == 0)
        return 0;
    QWidget* child = pierce(parent,point, chase_indicators);
    if (qobject_cast<QSplitterHandle*>(child)){
        QSplitter* parent = static_cast<QSplitter*>(child->parentWidget());
        child = parent->widget(parent->indexOf(child));
    }

    while (child && qobject_cast<QDockItem*>(child) == 0){
        child = child->parentWidget();
    }
    if (child){
        return qobject_cast<QDockItem*>(child);
    }
    return 0;

}


QWidget* lowestCommonAncestor(QWidget* a, QWidget* b){
    std::unordered_set<QWidget*> a_parents;
    QWidget* new_parent;
    QWidget* last_parent = 0;
    while (new_parent = a->parentWidget()){
        a_parents.insert(a);
        a = new_parent;
    }
    while (new_parent = b->parentWidget()){
        if (a_parents.find(b) != a_parents.end())
            return b;
        b = new_parent;
    }
    return 0;
}

int compareZOrder(QWidget* a, QWidget* b){
    QWidget* common_ancestor = lowestCommonAncestor(a,b);
    if (a == common_ancestor || b == common_ancestor){
        //TODO
    }
    QWidget* last_a;
    while (a != common_ancestor){
        last_a = a;
        a = a->parentWidget();
    }
    QWidget* last_b;
    while (b != common_ancestor){
        last_b = b;
        b = b->parentWidget();
    }
    QObjectList children = common_ancestor->children();
    int a_index = children.indexOf(a);
    int b_index = children.indexOf(b, a_index);
    if (b_index> -1)//b is on top
        return -1;
    // a is on top
    return 1;
}

QDockItem* QLayoutManager::baseDockFromPoint(Window target_window, const QPoint &point)
{
    QDockItem* ret = 0;
    for (QDockItem* bd:m_toplevel_docks){
        QRect rec = bd->rect();
        rec.moveTopLeft(bd->mapToGlobal(QPoint(0,0)));
        if (bd->isVisible() && rec.contains(point.x(),point.y())){
            if(UDock::DefaultWindowFinder::getDefaultWindowFinder()
                    ->isFirstDescendantOfSecond(bd->winId(),target_window)){
                if (ret == 0){
                    ret = bd;
                    continue;
                }

                //we have two candidates
                if (compareZOrder(ret,bd)<0){
                    //ret is behind bd
                    ret = bd;
                }
            }
        }
    }
    return ret;
}


QRect QLayoutManager::dockedSize(QDockItem *dragged_widget, QWidget *target_widget, DockOption dockoption)
{
    return dock(dragged_widget, target_widget, dockoption, true);
}

void QLayoutManager::dock(QDockItem *dragged_widget, QWidget *target_widget, DockOption dockoption)
{
    dock(dragged_widget, target_widget, dockoption, false);
}

QRect QLayoutManager::dock(QDockItem *dragged_widget, QWidget *target_widget, DockOption dockoption, bool simulate)
{

//    qDebug() << "dock";
    QBaseDock* base_dock = qobject_cast<QBaseDock*>(target_widget);
    if (base_dock){
        if (simulate){
            return base_dock->simulateDrop(dragged_widget, target_widget, dockoption);
        }
        else {
            base_dock->dock(dragged_widget, dockoption);
            return QRect();
        }
    }


    QDockItem* docktarget = qobject_cast<QDockItem*>(target_widget);
    if (docktarget){
        if (dockoption & DockOption::Tabbed) {
            return dockTabbed(dragged_widget, docktarget, simulate);
        }
        QDockItemContainer* container = qobject_cast<QDockItemContainer*>(docktarget);
        USplitter* splitter = qobject_cast<USplitter*>(container);
        if (splitter){
            int insertionindex = splitter->dockingIndex(dockoption);
            if (insertionindex != -1){
                if(simulate){
                    return splitter->simulateDrop(dragged_widget, target_widget, dockoption);
                }
                else {//not simulate
                    splitter->insertWidget(insertionindex, dragged_widget);
                    return QRect();
                }
            }
        }
        QDockItem* parent = docktarget->parentDockItem();
        if (parent){
            QDockItemContainer* parentcontainer = qobject_cast<QDockItemContainer*>(parent);
            if (parentcontainer){
                USplitter* existing_splitter = qobject_cast<USplitter*>(parentcontainer);

                if (existing_splitter){

                    int insertionindex = existing_splitter ->dockingIndex(dockoption, target_widget);
                    if (insertionindex != -1){
                        if (simulate){
                            return existing_splitter ->simulateDrop(dragged_widget, target_widget, dockoption);
                        }
                        else {
                            existing_splitter ->insertWidget(insertionindex, dragged_widget);
                            return QRect();
                        }
                    }
                }
                //we need create a new container
                if (dockoption & DockOption::CROSS){
                    QRect rec = target_widget->geometry();
                    rec.moveTopLeft(QPoint(0,0));
                     if (simulate){
//                         qDebug()<< "simulation";

                         ImitatorWidget* imitated_target = new ImitatorWidget(target_widget);
                         imitated_target->deleteLater();
                         USplitter* split = new USplitter(dockoption & DockOption::Vertical ? Qt::Vertical : Qt::Horizontal);
                         split->deleteLater();
                         split->insertWidget(0,imitated_target);
//                         qDebug() << "target rect: "<< rec;
                         rec.moveTopLeft(target_widget->mapToGlobal(QPoint(0,0)));
                         split->setGeometry(rec);
                 //        split->updateGeometry();
                 //        split->layout()->update();
                         QPixmap::grabWidget(split);
//                         qDebug() << "Docked size";
                         QRect ret = split->simulateDrop(dragged_widget, imitated_target, dockoption);
//                         qDebug() << ret;
                         return ret;
                     }
                     qDebug() << "QLayoutManager::dock " << "new container";
                     Qt::Orientation orient;
                     if (dockoption & DockOption::Vertical)
                         orient = Qt::Vertical;
                     else
                         orient = Qt::Horizontal;
                     USplitter* splitter = new USplitter(orient);

                     parentcontainer->replaceWidget(target_widget,splitter);
                     splitter->insertWidget(0,target_widget);
                     //fix because the widget might be hidden now
                     target_widget->setGeometry(rec);
                     target_widget->show();
                     if (dockoption & (DockOption::Left | DockOption::Top)){
                         splitter->insertWidget(0,dragged_widget);
                     }
                     else {
                         splitter->insertWidget(1,dragged_widget);
                     }

                 }
             }
        }
    }
    //TODO
}

QRect QLayoutManager::dockTabbed(QDockItem *dragged_widget, QDockItem *target_widget, bool simulate)
{
    if (simulate){
        //TOIMPROVE: more accurate simulation
        return QRect();
    }
    TabbedContainer* tabbed = qobject_cast<TabbedContainer*>(target_widget);
    if (tabbed == 0){
        tabbed = qobject_cast<TabbedContainer*>(target_widget->parentDockItem());
    }
    if (tabbed){
        //TODO
        tabbed->insertWidget(-1, dragged_widget);
        tabbed->setCurrentIndex(tabbed->count()-1);
        return QRect();
    }
    QDockItem* parent = target_widget->parentDockItem();

    QDockItemContainer* parentcontainer = qobject_cast<QDockItemContainer*>(parent);
    if (parentcontainer){
        tabbed = new TabbedContainer();
        parentcontainer->replaceWidget(target_widget, tabbed);
        tabbed->insertWidget(0,target_widget);
        tabbed->insertWidget(1,dragged_widget);
        tabbed->setCurrentIndex(1);
    }

}

void QLayoutManager::undock(QDockItem *widget)
{
    //TODO
}

std::unordered_set<QDockItem *> QLayoutManager::getBasicDocks()
{
    return m_toplevel_docks;
}

void QLayoutManager::addToplevelDock(QDockItem *base_dock)
{
    if (m_toplevel_docks.find(base_dock) != m_toplevel_docks.end()){
        return;
    }
    m_toplevel_docks.insert(base_dock);

}
